<?php
/**
 * abstract all the search functions
 *
 * @author jszhang
 */
require_once ( 'sphinxapi.php');

class Search {
/**
 * SphinxClient Object instance
 *
 * @var Java Object
 */
    var $m_sphinxClient;

    /**
     * index to search
     * may be an array
     * @var array
     */
    var $m_index = array("spider_content_index", "spider_content_index_stemmed");

    /**
     * all indexed field
     *
     * @var array
     */
    var $m_groupField;

    /**
     * default search match mode
     *
     * @var enum
     */
    var $m_matchMode = SPH_MATCH_EXTENDED2;

    /**
     * default rank type
     *
     * @var enum
     */
    var $m_ranker = SPH_RANK_PROXIMITY_BM25;

    /**
     * default group sort order is desc
     *
     * @var string
     */
    var $m_groupSort = "@group desc";

    /**
     * default result mode is array
     * @var boolean
     */
    var $m_isArrayResult = true;

    /**
     * one page display
     *
     * @var int
     */
    var $m_perPage = 20;

    /**
     *
     * default high light options
     *
     * @var array
     */
    var $m_highLightOpts = array
    (
    "before_match"          => "<span style=\"color:red;\">",
    "after_match"           => "</span>",
    "chunk_separator"       => " ... ",   //A string to insert between snippet chunks
    "limit"                 => 60,
    "around"                => 5,         //How much words to pick around each matching keywords block
    "exact_phrase"          => false,     //Whether to highlight exact query phrase matches only instead of individual keywords
    "single_passage"        => false,     //Whether to extract single best passage only.
    );


    function Search( $host='localhost', $port=3312 ) {
        $this->m_sphinxClient = new SphinxClient();
        $this->m_sphinxClient->SetServer( $host, $port );
    }

	function CleanKeyword( $keyword ){
		$keyword = trim( $keyword );
		// dirst clear sphinx search syntax charactor
		$keyword = preg_replace( "/[-&!=<|@*~^$\/\[\]]/", "",  $keyword );
		$keyword = str_replace( "\"", "",  $keyword );
		$keyword = str_replace( "\'", "",  $keyword );
		$keyword = str_replace( "\\", "",  $keyword );
		
		return $keyword;
	}

    /**
     * the simple query function does not involve sort, filter and group by
     * @param string $keyword
     * @param int    $page
     *
     * @return array
     */
    function Query( $keyword, $page=1 ) {
        $resultArray = array();

        $this->initialize();

        if ( intval($page) > 0 )
            $this->m_sphinxClient->SetLimits ( ($page-1)*$this->m_perPage, $this->m_perPage );

        $resultArray = $this->doSearch( $keyword );

        return $resultArray;
    }

    /**
     * query the old car, and order the result by a special field
     *
     * @param String $keyword
     * @param int $page
     * @param String $sortfield, the sort field can be a simple string, please use the constant defined in the config.php,
     * 							FIELD_PRICE, FIELD_PUBLISHDATE, FIELD_MILEAGE, FIELD_DATEOFMAKE
     * @param boolean $isdesc, if this variable set to be true, the results are sorted in desc order, else in asc order
     *
     */
     function SortQuery( $keyword, $sortfield, $page=1, $isdesc=true ) {
        $resultArray = array();

        $this->initialize();

        //set result limit
        if ( intval($page) > 0 )
            $this->m_sphinxClient->SetLimits ( ($page-1)*$this->m_perPage, $this->m_perPage );

        // set sort mode
        $sortmode = $isdesc ? SPH_SORT_ATTR_DESC : SPH_SORT_ATTR_ASC;
        if ( isset( $sortfield ) )
            $this->m_sphinxClient->SetSortMode ( $sortmode, $sortfield );

        $resultArray = $this->doSearch( $keyword );

        return $resultArray;
    }

    /**
     * search by multi filters
     *
     * @param String $keyword
     * @param array $conditions: the array format is,
     * @param int $page
     *
     */
     function MultiFilterQuery($keyword, $conditions, $page=1) {
        $resultArray = array();

        $this->initialize();

        //set result limit
        if ( intval($page) > 0 )
            $this->m_sphinxClient->SetLimits ( ($page-1)*$this->m_perPage, $this->m_perPage );
            
        //add fiter
        if ( count($conditions) > 0 ){
            foreach( $conditions as $filter=>$filtervals){
				if( $filter == "price" ){
					$ranges = explode( ",", $filtervals );
					//range must be 2
					assert( count($ranges) == 2 );
					$this->m_sphinxClient->SetFilterRange( $filter, $ranges[0], $ranges[1] );
				}
				else{
					$this->m_sphinxClient->SetFilter( $filter, $filtervals );
				}
            }
        }

        $resultArray = $this->doSearch( $keyword );

        return $resultArray;
    }

    /**
     * search by multi filters
     *
     * @param String $keyword
     * @param int $page
     */
     function MultiFilterAndSortQuery( $keyword, $conditions, $sortfield, $page=1, $isdesc=true) {
        $resultArray = array();

        $this->initialize();

        //set result limit
        if ( intval($page) > 0 )
            $this->m_sphinxClient->SetLimits ( ($page-1)*$this->m_perPage, $this->m_perPage );

        //add fiter
        if ( count($conditions) > 0 ){
            foreach( $conditions as $filter=>$filtervals){
				//when filter is price, it is range filter
				if( $filter == "price" ){
					$ranges = explode( ",", $filtervals );
					//range must be 2
					assert( count($ranges) == 2 );
					$this->m_sphinxClient->SetFilterRange( $filter, $ranges[0], $ranges[1] );
				}
				else{
					$this->m_sphinxClient->SetFilter( $filter, $filtervals );
				}
            }
        }

        // set sort mode
        $sortmode = $isdesc ? SPH_SORT_ATTR_DESC : SPH_SORT_ATTR_ASC;
        if ( isset( $sortfield ) )
            $this->m_sphinxClient->SetSortMode ( $sortmode, $sortfield );

        $resultArray = $this->doSearch( $keyword );

        return $resultArray;
    }

	/**
	 *
	 * @param string $keyword
	 * @param string $groupField
	 * @param boolean $isdesc
	 * @return array
	 */
     function GroupByQuery( $keyword, $groupField, $page=1, $isdesc=true ){
        $resultArray = array();
		
        $this->initialize();

        //set result limit
        if ( intval($page) > 0 )
            $this->m_sphinxClient->SetLimits ( ($page-1)*$this->m_perPage, $this->m_perPage );

        $groupSort = $isdesc?"@count desc":"@count asc";
        $this->m_sphinxClient->SetGroupBy( $groupField, SPH_GROUPBY_ATTR, $groupSort );

        $resultArray = $this->m_sphinxClient->Query( $keyword, $this->assembleIndex() );

        if ( $resultArray === false ) {
            print "Query failed: " . $this->m_sphinxClient->GetLastError() . ".\n";

        }
        else {
            if ( $this->m_sphinxClient->GetLastWarning() ) {
                print "WARNING: " . $this->m_sphinxClient->GetLastWarning() . "\n\n";
            }

            $resultArray = $this->printGroupByResult( $resultArray, $groupField );
        }

        return $resultArray;
    }

    //TODO
    // weight

    /**
     * build the search result high light result
     *
     * @param string $keyword
     * @param array $arrText
     *
     * @return array
     */
     function BuildHighLight( $keyword, $arrText ) {
        $res = array();

        //first get all string to result array
        $arrKeys = array();
        foreach( $arrText as $key=>$value ) {
            if( is_string( $value ) ) {
                $arrKeys[] = $key;
                $res[] = $value;
            }
        }

        //print_r( $res );

        // in BuildExcerpts function, only one index var will be accept
        $res = $this->m_sphinxClient->BuildExcerpts( $res, $this->m_index[0], $keyword, $this->m_highLightOpts );
        if ( !$res ) {
            echo "ERROR: " . $this->m_sphinxClient->GetLastError() ;
        }

        //assemble res to key value pair
        $res = array_combine( $arrKeys, $res );

        //print_r( $res );

        return $res;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    //protected functions

    /**
     * the function do the real search call sphinx query method
     * using the abstract printResult function defined in each sub class
     * @param string $keyword
     * @return array
     */
     function doSearch( $keyword ) {
        // weight testing
        //$this->m_sphinxClient->SetSortMode(SPH_SORT_EXTENDED, "@weight DESC");
//        $this->m_sphinxClient->SetSortMode( SPH_SORT_RELEVANCE );
//        $this->m_sphinxClient->SetFieldWeights( array( "nick" => 1, "shop_name" => 100 ) );

        $result = $this->m_sphinxClient->Query( $keyword, $this->assembleIndex() );

        if ( $result === false ) {
            print "Query failed: " . $this->m_sphinxClient->GetLastError() . ".\n";

        }
        else {
            if ( $this->m_sphinxClient->GetLastWarning() ) {
                print "WARNING: " . $this->m_sphinxClient->GetLastWarning() . "\n\n";
            }

            return $this->printResult( $result );
        }

        return $result;
    }

    /**
     * This function is a help function to build up the index path string for all query
     * @return string
     */
     function assembleIndex() {
        return implode(',', $this->m_index );
    }

    /**
     * this function is set to abstract
     * as it is set all the search options
     *
     * different sub classes may have different settings
     */
     function initialize(){
	//clear all filters
	$this->m_sphinxClient->ResetFilters();
	$this->m_sphinxClient->ResetOverrides();
	$this->m_sphinxClient->ResetGroupBy();

        $this->m_sphinxClient->SetConnectTimeout ( 1 );
        $this->m_sphinxClient->SetMatchMode ( $this->m_matchMode );
        $this->m_sphinxClient->SetWeights ( array ( 100, 1 ) );

	$this->m_sphinxClient->SetRankingMode ( $this->m_ranker );
        $this->m_sphinxClient->SetArrayResult ( $this->m_isArrayResult );
        $this->m_perPage = 20;
	}

    /**
     * assemble the result in a special order
     *
     * @param array $results
     * @return array
     */
     function printResult( $res ){
	$result = array();
        
        $result['total'] = $res['total'];
        $result['total_found'] = $res['total_found'];
        $result['time'] = $res['time'];

	if ( isset($res["matches"]) && is_array( $res["matches"] ) ) {
            $result['matchIds'] = array();
            foreach ( $res["matches"] as $docinfo ) {
		
                $result['matchIds'][] = $docinfo['id'];
            }
        }

        return $result;
	}


    /**
     * assemble the group by result
     *
     * @param array $results
     * @return array
     */
     function printGroupByResult( $res, $groupField ){
	$result = array();

        $result['total'] = $res['total'];
        $result['total_found'] = $res['total_found'];

		if ( isset($res["matches"]) && is_array( $res["matches"] ) ) {
            $result['matches'] = array();
            foreach ( $res["matches"] as $docinfo ) {
                $result['matches'][$docinfo['attrs']['@groupby']] = array(
					'count' => $docinfo['attrs']['@count']
				);
            }
        }

	return $result;
	}
}
?>
